{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "be9ada0d",
   "metadata": {},
   "source": [
    "## Cookie"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75327478",
   "metadata": {},
   "source": [
    "### 原理"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f63314f5",
   "metadata": {},
   "source": [
    "Web 应用程序 是使用 HTTP协议传输数据的。\n",
    "\n",
    "HTTP协议是无状态的协议。\n",
    "\n",
    "一旦数据交换完毕，客户端 与 服务器端 的连接就会关闭。\n",
    "\n",
    "再次交换数据需要建立新的连接，这就意味着 服务器 无法从连接上跟踪会话。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1880b66c",
   "metadata": {},
   "source": [
    "即 用户A 购买了一件商品放入购物车内，当再次购买商品时服务器已经无法判断该购买行为是属于 用户A 的会话还是 用户B 的会话了。\n",
    "\n",
    "因此，必须引入一种机制，让服务器记住用户。Cookie 就是这样的一种机制。它可以弥补HTTP协议无状态的不足"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "547cd45a",
   "metadata": {},
   "source": [
    "服务器单从网络连接上无从知道客户身份。怎么办呢？\n",
    "\n",
    "就给客户端们颁发一个通行证吧，每人一个，\n",
    "\n",
    "无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca3acaf6",
   "metadata": {},
   "source": [
    "![](04_Flask高级_images/1.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "514bc4ab",
   "metadata": {},
   "source": [
    "Cookie 实际上是一小段的文本信息。\n",
    "\n",
    "客户端请求服务器，如果服务器需要记录该用户状态，就使用response 向客户端浏览器颁发一个Cookie。\n",
    "\n",
    "客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时，浏览器把请求的网址连同该Cookie一同提交给服务器。\n",
    "\n",
    "服务器检查该Cookie，以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。\n",
    "\n",
    "注意：浏览器对cookie数量和大小有限制的！如果超过了这个限制，你的信息将丢失。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81b7eb1a",
   "metadata": {},
   "source": [
    "### Flask 操作 Cookie"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5926bc5c",
   "metadata": {},
   "source": [
    "![](04_Flask高级_images/2.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fad73867",
   "metadata": {},
   "source": [
    "**设置**\n",
    "\n",
    "设置 cookie 是在 Response 的对象上设置\n",
    "\n",
    "flask.Response 对象有一个 set_cookie 方法，可以通过这个方法来设置 cookie信息。 \n",
    "\n",
    "key, value形式设置信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "aafc6ead",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T11:43:27.743643Z",
     "start_time": "2023-12-15T11:42:47.633455Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 19:42:51] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 19:42:51] \"GET /favicon.ico HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Dec/2023 19:43:02] \"GET /set_cookie/ HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 19:43:10] \"GET /get_cookie/ HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 19:43:20] \"GET /del_cookie/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask, make_response, request\n",
    "\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello'\n",
    "\n",
    "\n",
    "@app.route('/set_cookie/')\n",
    "def set_cookie():\n",
    "    \"\"\"设置cookie\"\"\"\n",
    "    \n",
    "    # 响应对象\n",
    "    resp = make_response(\"设置了一个Cookie信息\")\n",
    "    \n",
    "    # 设置cookie\n",
    "    resp.set_cookie('uname', 'itbaizhan')\n",
    "    \n",
    "    # 返回响应\n",
    "    return resp\n",
    "\n",
    "@app.route('/get_cookie/')\n",
    "def get_cookie():\n",
    "    \"\"\"获取cookie\"\"\"\n",
    "    \n",
    "    # 获取 请求对象中的 cookie信息\n",
    "    uname = request.cookies.get('uname')\n",
    "    \n",
    "    # 返回响应\n",
    "    return f'Cookie里面的内容是:{uname}'\n",
    "\n",
    "@app.route('/del_cookie/')\n",
    "def del_cookie():\n",
    "    \"\"\"删除cookie\"\"\"\n",
    "    \n",
    "    # 响应对象\n",
    "    resp = make_response(\"删除了一个Cookie信息\")\n",
    "    \n",
    "    # 删除 cookie信息\n",
    "    resp.delete_cookie('uname')\n",
    "    \n",
    "    # 返回响应\n",
    "    return resp\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e609a931",
   "metadata": {},
   "source": [
    "### Cookie 有效期"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b845a86b",
   "metadata": {},
   "source": [
    "**默认的过期时间：** 如果没有显示的指定过期时间，那么这个 cookie 将会在浏览器关闭后过期\n",
    "\n",
    "**max_age：** 以秒为单位，距离现在多少秒后cookie会过期\n",
    "\n",
    "**expires：** 为datetime类型。这个时间需要设置为格林尼治时间，相对北京时间来说 会自动+8小时\n",
    "\n",
    "如果 max_age 和 expires 都设置了，那么这时候以 max_age 为标准"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "05c51460",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T11:50:49.444907Z",
     "start_time": "2023-12-15T11:50:29.069365Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 19:50:30] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 19:50:43] \"GET /create_cookie/max_age/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,Response\n",
    "\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello！！'\n",
    "\n",
    "@app.route('/create_cookie/defualt/')\n",
    "def create_cookie1():\n",
    "    \"\"\"创建cookie\"\"\"\n",
    "    \n",
    "    # 创建 响应对象\n",
    "    resp = Response('通过默认值，设置cookie有效期')\n",
    "    \n",
    "    # 如果没有设置有效期，默认会在浏览器关闭的时候，cookie过期\n",
    "    resp.set_cookie('uname', 'sxt')\n",
    "    \n",
    "    # 返回响应\n",
    "    return resp\n",
    "\n",
    "@app.route('/create_cookie/max_age/')\n",
    "def create_cookie2():\n",
    "    \"\"\"创建cookie\"\"\"\n",
    "    \n",
    "    # 创建 响应对象\n",
    "    resp = Response('通过max_age，设置cookie有效期')\n",
    "    \n",
    "    # max_age以秒为单位设置cookie的有效期\n",
    "    age = 60*60*2\n",
    "    resp.set_cookie('uname', 'itbaizhan', max_age=age)\n",
    "    \n",
    "    # 返回响应\n",
    "    return resp\n",
    "\n",
    "from datetime import datetime\n",
    "\n",
    "@app.route('/create_cookie/expires/')\n",
    "def create_cookie3():\n",
    "    \"\"\"创建cookie\"\"\"\n",
    "    \n",
    "    # 创建 响应对象\n",
    "    resp = Response('通过expires，设置cookie有效期')\n",
    "    \n",
    "    # expires 以指定时间为 cookie的有效期\n",
    "    tmp_time = datetime(2021, 11, 11, hour=18, minute=0, second=0)\n",
    "    resp.set_cookie('uname', 'python', expires=tmp_time)\n",
    "    \n",
    "    # 返回响应\n",
    "    return resp\n",
    "\n",
    "from datetime import timedelta\n",
    "\n",
    "@app.route('/create_cookie/expires2/')\n",
    "def create_cookie4():\n",
    "    \"\"\"创建cookie\"\"\"\n",
    "    \n",
    "    # 创建 响应对象\n",
    "    resp = Response('通过expires，设置cookie有效期')\n",
    "    \n",
    "    # expires 以指定时间为cookie的有效期\n",
    "    tmp_time = datetime.now() + timedelta(days=2)\n",
    "    resp.set_cookie('uname', 'python_sql', expires=tmp_time)\n",
    "    \n",
    "    # 返回响应\n",
    "    return resp\n",
    "\n",
    "\n",
    "@app.route('/create_cookie/exp_max/')\n",
    "def create_cookie5():\n",
    "    \"\"\"创建cookie\"\"\"\n",
    "    \n",
    "    # 创建响应对象\n",
    "    resp = Response('通过expires与max_age，设置cookie有效期')\n",
    "    \n",
    "    # expires 与max_age同时设置了，会以max_age为准\n",
    "    tmp_time = datetime.now() + timedelta(days=2)\n",
    "    resp.set_cookie('uname', 'python_sql', expires=tmp_time, max_age = 60*60*2)\n",
    "    \n",
    "    # 返回响应\n",
    "    return resp\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7f1a9b7",
   "metadata": {},
   "source": [
    "## Session"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3781d0c",
   "metadata": {},
   "source": [
    "### 原理"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1817d7b",
   "metadata": {},
   "source": [
    "Session 和 Cookie 的作用有点类似，都是为了存储用户相关的信息，都是为了解决http协议无状态的这个特点。\n",
    "\n",
    "不同的是Cookie保存在客户端浏览器中，而Session保存在服务器上。\n",
    "\n",
    "客户端浏览器访问服务器的时候，服务器把客户端信息以某种形式记录在服务器上。\n",
    "\n",
    "客户端浏览器再次访问时只需要从 该Session中 查找该客户的状态就可以了。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63354955",
   "metadata": {},
   "source": [
    "Session的出现，是为了解决cookie存储数据不安全的问题的。\n",
    "\n",
    "如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话，\n",
    "\n",
    "那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。\n",
    "\n",
    "Session相当于程序在服务器上建立的一份客户档案，客户来访的时候只需要查询客户档案表就可以了。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64324e9f",
   "metadata": {},
   "source": [
    "![](04_Flask高级_images/3.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d7d61869",
   "metadata": {},
   "source": [
    "存储在 服务器的数据 会更加的安全，不容易被窃取。\n",
    "\n",
    "但存储在服务器也有一定的弊端，就是会占用服务器的资源，但现在服务器已经发展至今，一些session信息还是绰绰有余的。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02beaf32",
   "metadata": {},
   "source": [
    "### Flask 操作 Session"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "146b2bfd",
   "metadata": {},
   "source": [
    "![](04_Flask高级_images/4.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "899318a9",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T11:59:25.185411Z",
     "start_time": "2023-12-15T11:58:56.454294Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 19:58:58] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 19:59:09] \"GET /set_session/ HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 19:59:20] \"GET /get_session/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,session\n",
    "from os import urandom\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "# 直接设置session的盐\n",
    "app.secret_key = urandom(24)\n",
    "\n",
    "'''\n",
    "# 也可以通过类的方式 加盐\n",
    "class DefaultConfig:\n",
    "    SECRET_KEY = 'sdfsdf'\n",
    "app.config.from_object(DefaultConfig)\n",
    "'''\n",
    "\n",
    "\n",
    "@app.route('/set_session/')\n",
    "def set_session():\n",
    "    \"\"\"设置seesion\"\"\"\n",
    "    \n",
    "    session['uname'] = 'ITbaizhan'\n",
    "    session['pwd'] = '123456'\n",
    "    \n",
    "    # 返回响应\n",
    "    return '设置一个Session'\n",
    "\n",
    "@app.route('/get_session/')\n",
    "def get_session():\n",
    "    \"\"\"获取session\"\"\"\n",
    "    uname = session.get('uname')\n",
    "    pwd = session.get('pwd')\n",
    "    \n",
    "    # 返回响应\n",
    "    return f'从Session读取出来的数据是：{uname}==={pwd}'\n",
    "\n",
    "@app.route('/del_session/')\n",
    "def del_session():\n",
    "    \"\"\"删除session\"\"\"\n",
    "    # pop 删除一个key\n",
    "    session.pop('uname')\n",
    "    \n",
    "    # clear 清空所有的数据\n",
    "    session.clear()\n",
    "    \n",
    "    # 返回响应\n",
    "    return '删除session中uname信息'\n",
    "\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello'\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0879339",
   "metadata": {},
   "source": [
    "### Session 有效期"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "048983cd",
   "metadata": {},
   "source": [
    "如果没有设置session的有效期。 那么默认就是浏览器关闭后过期。\n",
    "\n",
    "如果设置session.permanent=True，那么就会默认在31天后过期。\n",
    "\n",
    "如果不想在31天后过期，按如下步骤操作：\n",
    "1. session.permanent=True\n",
    "2. 可以设置 app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hour=2) 在两个小时后过期"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "505ccf08",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T12:03:26.316174Z",
     "start_time": "2023-12-15T12:03:05.085816Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 20:03:06] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 20:03:16] \"GET /set_session/ HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 20:03:20] \"GET /get_session/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,session\n",
    "from datetime import timedelta\n",
    "\n",
    "# app 对象\n",
    "app = Flask(__name__)\n",
    "\n",
    "# 设置 session 盐\n",
    "app.secret_key = 'sdfdfdsfsss'\n",
    "\n",
    "# 设置 session 有效期\n",
    "app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=2)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello！！'\n",
    "\n",
    "@app.route('/set_session/')\n",
    "def set_session():\n",
    "    \"\"\"设置session\"\"\"\n",
    "    \n",
    "    # 设置session的持久化, 默认是增加了31天\n",
    "    session.permanent = True\n",
    "    session['uname'] = '10001'\n",
    "    \n",
    "    # 返回响应\n",
    "    return '设置一个Session的信息'\n",
    "\n",
    "\n",
    "@app.route('/get_session/')\n",
    "def get_session():\n",
    "    \"\"\"获取session\"\"\"\n",
    "    \n",
    "    # 如果服务器关闭掉了，session的有效期，依然是之前系统保存日期\n",
    "    # 如果secret_key设置是一个固定的值，那么服务器重启不会影响session的有效器\n",
    "    # 如果secret_key设置不是一个固定的值，那么服务器之前设置的session将全部过期\n",
    "    return session.get('uname')\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91c3ddc1",
   "metadata": {},
   "source": [
    "### 实战：用户免登录"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5aec698c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from flask import Flask,views,render_template,request,session,redirect,url_for\n",
    "\n",
    "# app 对象\n",
    "app = Flask(__name__)\n",
    "\n",
    "# 设置 session 盐\n",
    "app.secret_key ='asdfsfsfssvsdb'\n",
    "\n",
    "@app.route('/')\n",
    "def home():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello！！'\n",
    "\n",
    "\n",
    "class LoginView(views.MethodView):\n",
    "    def __jump(self,msg = None):\n",
    "        return render_template('session免登录_login.html',msg = msg)\n",
    "    \n",
    "    \n",
    "    def get(self):\n",
    "        msg = request.args.get('msg')\n",
    "        return self.__jump(msg = msg)\n",
    "    \n",
    "    \n",
    "    def post(self):\n",
    "        uname = request.form.get('uname')\n",
    "        pwd = request.form.get('pwd')\n",
    "\n",
    "        if 'sxt' == uname and '123' == pwd:\n",
    "            # 记录用户信息\n",
    "            session['uname'] = uname\n",
    "            return render_template('session免登录_index.html')\n",
    "        return self.__jump(msg='用户名或密码不正确')\n",
    "\n",
    "app.add_url_rule('/login/',view_func=LoginView.as_view('login'))\n",
    "\n",
    "@app.route('/index/')\n",
    "def index():\n",
    "    \n",
    "    # 获取用户名\n",
    "    uname = session.get('uname')\n",
    "    if  uname:\n",
    "        return render_template('index.html')\n",
    "    else:\n",
    "        return redirect(url_for('login',msg='请先登录！！'))\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8d6c682",
   "metadata": {},
   "source": [
    "## Local 对象"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2dd66756",
   "metadata": {},
   "source": [
    "要实现并发效果, 每一个请求进来的时候我们都开启一个进程, 这显然是不合理的, 于是就可以使用线程\n",
    "\n",
    "那么线程中数据互相不隔离，存在修改数据的时候数据不安全的问题\n",
    "\n",
    "![](04_Flask高级_images/5.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e035a322",
   "metadata": {},
   "source": [
    "通过 local 对象，实现 线程之间 数据的互不干扰"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2fe3ed72",
   "metadata": {},
   "source": [
    "### thread 中的 local"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "799bc54b",
   "metadata": {},
   "source": [
    "Python提供了 ThreadLocal 变量，它本身是一个全局变量，\n",
    "\n",
    "但是每个线程却可以利用它来保存属于自己的私有数据，这些私有数据对其他线程也是不可见的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "4f1509e7",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T12:17:13.766368Z",
     "start_time": "2023-12-15T12:17:13.755822Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "子线程的内容：sxt\n",
      "主线程内容：这个是请求的数据1\n"
     ]
    }
   ],
   "source": [
    "from threading import Thread,local\n",
    "\n",
    "# 创建 local 对象\n",
    "local = local()\n",
    "\n",
    "# 主线程在此 存储数据\n",
    "local.request='这个是请求的数据1'\n",
    "\n",
    "class MyThread(Thread):\n",
    "    def run(self):\n",
    "        \n",
    "        # 子线程在此 存储数据\n",
    "        local.request='sxt'\n",
    "        print(f'子线程的内容：{local.request}')\n",
    "\n",
    "\n",
    "my_thread = MyThread()\n",
    "my_thread.start()\n",
    "my_thread.join()\n",
    "\n",
    "# 通过local 使线程间的数据 互补干扰\n",
    "print(f'主线程内容：{local.request}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7156011",
   "metadata": {},
   "source": [
    "### werkzeug 中的 local"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "16eadd6d",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T12:20:11.860960Z",
     "start_time": "2023-12-15T12:20:11.847050Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "子线程的内容：sxt\n",
      "主线程内容：这个是请求的数据1\n"
     ]
    }
   ],
   "source": [
    "from threading import Thread\n",
    "from werkzeug.local import Local\n",
    "\n",
    "local = Local()\n",
    "local.request='这个是请求的数据1'\n",
    "\n",
    "class MyThread(Thread):\n",
    "    def run(self):\n",
    "        local.request='sxt'\n",
    "        print(f'子线程的内容：{local.request}')\n",
    "\n",
    "\n",
    "my_thread = MyThread()\n",
    "my_thread.start()\n",
    "my_thread.join()\n",
    "\n",
    "print(f'主线程内容：{local.request}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b4df681",
   "metadata": {},
   "source": [
    "## 应用上下文"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "262698ee",
   "metadata": {},
   "source": [
    "![](04_Flask高级_images/6.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "698cebbe",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T12:28:58.027988Z",
     "start_time": "2023-12-15T12:28:52.458803Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "__main__\n",
      "__main__\n",
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 20:28:54] \"GET / HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,current_app\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "# 方法1\n",
    "# 将上下文推入stack中\n",
    "app_ctx = app.app_context()\n",
    "app_ctx.push()\n",
    "\n",
    "# 方可读取 App名字\n",
    "print(current_app.name)\n",
    "\n",
    "# 方法2\n",
    "with app.app_context():\n",
    "    print(current_app.name)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    return f'Hello！ 这个是一个 [{current_app.name}] 引用'\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c871b6fa",
   "metadata": {},
   "source": [
    "## 请求上下文"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4ec79a3",
   "metadata": {},
   "source": [
    "请求上下文：请求上下文也是存放到一个 LocalStack 的栈中。\n",
    "\n",
    "和请求相关的操作就必须用到请求上下文，比如使用 url_for 反转视图函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "9b579c49",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T12:31:17.784266Z",
     "start_time": "2023-12-15T12:31:04.833245Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/test/\n",
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 20:31:06] \"GET / HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,url_for\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    url = url_for('test_url')\n",
    "    return f'Hello！==={url}'\n",
    "\n",
    "@app.route('/test/')\n",
    "def test_url():\n",
    "    return '这个是为了测试请求上下文'\n",
    "    \n",
    "\n",
    "with app.test_request_context():\n",
    "    url = url_for('test_url')\n",
    "    print(url)\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b61fc00e",
   "metadata": {},
   "source": [
    "## 全局 g对象"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7515473a",
   "metadata": {},
   "source": [
    "g对象 是在整个Flask应用运行期间都是可以使用的\n",
    "\n",
    "并且也跟request一样，是线程隔离的\n",
    "\n",
    "这个对象是专门用来存储开发者自己定义的一些数据，方便在整个Flask程序中都可以使用\n",
    "\n",
    "一般使用就是，将一些经常会用到的数据绑定到上面，以后就直接从g上面取就可以了，而不需要通过传参的形式，这样更加方便。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7b2ca22e",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T12:38:15.251385Z",
     "start_time": "2023-12-15T12:38:04.686586Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 20:38:05] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 20:38:10] \"GET /new/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask, g\n",
    "\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "def func_a(uname):\n",
    "    return f'func_a uname:{uname}'\n",
    "\n",
    "def func_b(uname):\n",
    "    return f'func_b uname:{uname}'\n",
    "\n",
    "def func_c(uname):\n",
    "    return f'func_c uname:{uname}'\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    uname = 'baizhan'\n",
    "    a = func_a(uname)\n",
    "    b = func_b(uname)\n",
    "    c = func_c(uname)\n",
    "\n",
    "    return f'Hello！ <br> {a} <br> {b} <br> {c}'\n",
    "\n",
    "\n",
    "\n",
    "def func_a1():\n",
    "    return f'func_a uname:{g.uname}'\n",
    "\n",
    "def func_b1():\n",
    "    return f'func_b uname:{g.uname}'\n",
    "\n",
    "def func_c1():\n",
    "    return f'func_c uname:{g.uname}'\n",
    "\n",
    "@app.route('/new/')\n",
    "def index_new():\n",
    "    uname = 'baizhan'\n",
    "    g.uname = uname\n",
    "    a = func_a1()\n",
    "    b = func_b1()\n",
    "    c = func_c1()\n",
    "\n",
    "    return f'Hello！ <br> {a} <br> {b} <br> {c}'\n",
    "\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a809b8e7",
   "metadata": {},
   "source": [
    "## 钩子函数"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26562d9a",
   "metadata": {},
   "source": [
    "在Flask中钩子函数是使用特定的装饰器装饰的函数\n",
    "\n",
    "为什么叫做钩子函数呢，是因为钩子函数可以在正常执行的代码中，插入一段自己想要执行的代码。那么这种函数就叫做钩子函数。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1606a89",
   "metadata": {},
   "source": [
    "![](04_Flask高级_images/7.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c718b495",
   "metadata": {},
   "source": [
    "**常见的钩子函数：**\n",
    "\n",
    "**before_first_request：** 处理项目的第一次请求之前执行。\n",
    "\n",
    "**before_request：** 在每次请求之前执行。\n",
    "\n",
    "**teardown_appcontext：** 不管是否有异常，注册的函数都会在每次请求之后执行。\n",
    "\n",
    "**template_filter：** 在使用Jinja2模板的时候自定义过滤器。\n",
    "\n",
    "**context_processor：** 上下文处理器。使用这个钩子函数，必须返回一个字典。\n",
    "- 这个字典中的值在所有模版中都可以使用。这个钩子函数的函数是，如果一些在很多模版中都要用到的变量，那么就可以使用这个钩子函数来返回，而不用在每个视图函数中的 render_template 中去写，这样可以让代码更加简洁和好维护。\n",
    "\n",
    "**errorhandler：** errorhandler接收状态码，可以自定义返回这种状态码的响应的处理方法。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8aca3643",
   "metadata": {},
   "source": [
    "### before_first_request"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "9e16b080",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T12:43:22.220366Z",
     "start_time": "2023-12-15T12:43:15.816805Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 20:43:17] \"GET / HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "这个是第一次请求时，需要运行的逻辑！！\n",
      "Hello!!\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    print(\"Hello!!\")\n",
    "    return 'Hello'\n",
    "\n",
    "@app.before_first_request\n",
    "def before_first():\n",
    "    print('这个是第一次请求时，需要运行的逻辑！！')\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c80cfeb",
   "metadata": {},
   "source": [
    "### before_request"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "531ff7b8",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T12:46:18.773898Z",
     "start_time": "2023-12-15T12:45:45.258777Z"
    },
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 20:45:46] \"GET / HTTP/1.1\" 404 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "运行代码 111111111111\n",
      "这个是每次请求时，需要执行的逻辑！！！\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "127.0.0.1 - - [15/Dec/2023 20:45:59] \"GET /home HTTP/1.1\" 308 -\n",
      "127.0.0.1 - - [15/Dec/2023 20:45:59] \"GET /home/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "运行代码 111111111111\n",
      "这个是每次请求时，需要执行的逻辑！！！\n",
      "运行代码 111111111111\n",
      "这个是每次请求时，需要执行的逻辑！！！\n",
      "运行代码 3333333333\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "127.0.0.1 - - [15/Dec/2023 20:46:05] \"GET /index HTTP/1.1\" 404 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "运行代码 111111111111\n",
      "这个是每次请求时，需要执行的逻辑！！！\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "127.0.0.1 - - [15/Dec/2023 20:46:12] \"GET /login HTTP/1.1\" 308 -\n",
      "127.0.0.1 - - [15/Dec/2023 20:46:12] \"GET /login/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "运行代码 111111111111\n",
      "这个是每次请求时，需要执行的逻辑！！！\n",
      "运行代码 111111111111\n",
      "这个是每次请求时，需要执行的逻辑！！！\n",
      "运行代码 222222222\n",
      "Hello!!!!\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,g,session\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "# 设置 session 盐\n",
    "app.config['SECRET_KEY'] = 'skidhfikshighsd'\n",
    "\n",
    "@app.route('/login/')\n",
    "def login():\n",
    "    \"\"\"登录\"\"\"\n",
    "    print('运行代码 222222222')\n",
    "    print('Hello!!!!')\n",
    "    \n",
    "    # 设置 session\n",
    "    session['uname'] = '吕布'\n",
    "    return f'Hello'\n",
    "\n",
    "@app.route('/home/')\n",
    "def home():\n",
    "    print('运行代码 3333333333')\n",
    "    \n",
    "    # 判断 g对象 是否拥有 uname 属性\n",
    "    if hasattr(g, 'uname'):\n",
    "        return f'用户已登录！用户名是：{g.uname}'\n",
    "    return '用户没有登录！'\n",
    "\n",
    "@app.before_request\n",
    "def before_request():\n",
    "    \"\"\"每次请求之前，都会执行\"\"\"\n",
    "    \n",
    "    print('运行代码 111111111111')\n",
    "    \n",
    "    # 获取 session数据\n",
    "    uname = session.get('uname')\n",
    "    if uname:\n",
    "        g.uname = uname\n",
    "    print('这个是每次请求时，需要执行的逻辑！！！')\n",
    "\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84e8809e",
   "metadata": {},
   "source": [
    "### context_processor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "b42116fd",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T12:51:12.482691Z",
     "start_time": "2023-12-15T12:49:55.719548Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 20:49:57] \"GET / HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "222222222222\n",
      "111111111111111\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "127.0.0.1 - - [15/Dec/2023 20:51:06] \"GET /home/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3333333333333\n",
      "111111111111111\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,render_template\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    print('222222222222')\n",
    "    return render_template('context_processor_index.html')\n",
    "\n",
    "@app.route('/home/')\n",
    "def home():\n",
    "    print('3333333333333')\n",
    "    return render_template('context_processor_home.html')\n",
    "\n",
    "@app.context_processor\n",
    "def context_processor():\n",
    "    \"\"\"上下文处理器：返回的字典，在所有模版中均可使用\"\"\"\n",
    "    print('111111111111111')\n",
    "    return {'uname':'吕布'}\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "36699d30",
   "metadata": {},
   "source": [
    "### errorhandler"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ecd24238",
   "metadata": {},
   "source": [
    "在errorhandler装饰的钩子函数下，记得要返回相应的状态码。\n",
    "\n",
    "在errorhandler装饰的钩子函数中，必须要写一个参数，来接收错误的信息，如果没有参数，就会直接报错。\n",
    "\n",
    "使用 flask.abort 可以手动的抛出相应的错误，比如开发者在发现参数不正确的时候可以自己手动的抛出一个400错误"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "55b89b97",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T13:01:23.241532Z",
     "start_time": "2023-12-15T13:00:17.347545Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 21:00:18] \"GET / HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,g,render_template,abort\n",
    "\n",
    "app =Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \n",
    "    # 没有资源，返回 500 -- 触发对应钩子函数\n",
    "    print(g.uname)\n",
    "    \n",
    "    return 'Hello!'\n",
    "\n",
    "@app.errorhandler(500)\n",
    "def server_error(error):\n",
    "    \n",
    "    # 状态码虽然可以不写，但是推荐写上，这样可以告诉服务器是哪个错误\n",
    "    return render_template('500.html'), 500 \n",
    "\n",
    "\n",
    "@app.errorhandler(404)\n",
    "def server_error(error): \n",
    "    return render_template('404.html'), 404\n",
    "\n",
    "\n",
    "@app.route('/home/')\n",
    "def home():\n",
    "    \n",
    "    # 手动抛出 404 错误 -- 触发对应钩子函数\n",
    "    abort(404)\n",
    "\n",
    "\n",
    "if __name__ ==\"__main__\":\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12390919",
   "metadata": {},
   "source": [
    "## 信号机制"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45b94d3c",
   "metadata": {},
   "source": [
    "### 自定义信号"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65fcb39b",
   "metadata": {},
   "source": [
    "自定义信号步骤：\n",
    "1. 创建一个信号\n",
    "2. 监听一个信号\n",
    "3. 发送一个信号"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "21a67333",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T13:11:31.625857Z",
     "start_time": "2023-12-15T13:11:23.703140Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 21:11:25] \"GET / HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "我的意大利炮！！开火！！ zxy\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask\n",
    "from blinker import Namespace\n",
    "\n",
    "# 创建 app 对象\n",
    "app = Flask(__name__)\n",
    "\n",
    "\n",
    "\n",
    "# 1. 定义信号：需要使用到 blinker这个包的 Namespace类 来创建一个命名空间\n",
    "space = Namespace()\n",
    "fire_signal = space.signal('发送一个信号')\n",
    "\n",
    "# 2. 监听信号\n",
    "# 设置 回调函数\n",
    "def start_fire(sender, uname):\n",
    "    print('我的意大利炮！！开火！！', uname)\n",
    "\n",
    "# 将 信号 与 回调函数 连接\n",
    "fire_signal.connect(start_fire)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    # 3. 发送信号，可以传递参数\n",
    "    fire_signal.send(uname=\"zxy\")\n",
    "    \n",
    "    # 返回响应\n",
    "    return 'Hello!!'\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0d73a42",
   "metadata": {},
   "source": [
    "### 实战：存储登录日志"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "d507956a",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T13:20:02.544259Z",
     "start_time": "2023-12-15T13:18:57.626016Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 21:19:00] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 21:19:09] \"GET /login/ HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 21:19:45] \"GET /login?uname=zxy HTTP/1.1\" 308 -\n",
      "127.0.0.1 - - [15/Dec/2023 21:19:45] \"GET /login/?uname=zxy HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,request,g\n",
    "from blinker import Namespace\n",
    "\n",
    "# 1、定义信号，命名为‘登录’\n",
    "space = Namespace()\n",
    "login_space = space.signal('登录')\n",
    "\n",
    "# 2、监听信号\n",
    "# 定义 回调函数\n",
    "def login_signal(sender):\n",
    "    \n",
    "    # 请求的 IP地址\n",
    "    ip = request.remote_addr\n",
    "    \n",
    "    # 保存信息\n",
    "    info = f'{ip}:{g.name}'\n",
    "    with open('data/login.log','a',encoding='utf-8') as f:\n",
    "        f.write(info+'\\n')\n",
    "\n",
    "# 连接 信号 与 回调函数\n",
    "login_space.connect(login_signal)\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello!!'\n",
    "\n",
    "@app.route('/login/')\n",
    "def login():\n",
    "    \"\"\"登录界面\"\"\"\n",
    "    \n",
    "    # 获取 请求对象中的 用户名\n",
    "    uname = request.args.get('uname')\n",
    "    \n",
    "    # 如果用户名存在\n",
    "    if uname:\n",
    "        \n",
    "        # 保存在 g 对象中\n",
    "        g.name = uname\n",
    "        \n",
    "        # 3、发送信号\n",
    "        login_space.send()\n",
    "        \n",
    "        # 返回响应\n",
    "        return '登录成功！！'\n",
    "    else:\n",
    "        \n",
    "        # 返回响应\n",
    "        return '输入的用户名或密码不正确'\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "44914fda",
   "metadata": {},
   "source": [
    "### 内置信号"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22184245",
   "metadata": {},
   "source": [
    "**Flask内置了10个常用的信号：**\n",
    "\n",
    "**1 template_rendered：** 模版渲染完成后的信号\n",
    "\n",
    "**2 before_render_template：** 模版渲染之前的信号\n",
    "\n",
    "**3 request_started：** 请求开始之前，在到达视图函数之前发送信号。\n",
    "\n",
    "**4 request_finished：** 请求结束时，在响应发送给客户端之前发送信号。\n",
    "\n",
    "**5 request_tearing_down：** 请求对象被销毁时发送的信号，即使在请求过程中发生异常也会发送信号。\n",
    "\n",
    "**6 got_request_exception：** 在请求过程中抛出异常时发送信号，异常本身会通过exception传递到订阅（监听）的函数中。一般可以监听这个信号，来记录网站异常信息。\n",
    "\n",
    "**7 appcontext_tearing_down：** 应用上下文被销毁时发送的信号。\n",
    "\n",
    "**8 appcontext_pushed：** 应用上下文被推入到栈上时发送的信号。\n",
    "\n",
    "**9 appcontext_popped：** 应用上下文被推出栈时发送的信号。\n",
    "\n",
    "**10 message_flashed：** 调用了Flask的 flash 方法时发送的信号。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3856edcf",
   "metadata": {},
   "source": [
    "#### template_rendered"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "05ea58d2",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T13:24:26.781458Z",
     "start_time": "2023-12-15T13:24:12.499942Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 21:24:14] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 21:24:19] \"GET /home/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Flask '__main__'>\n",
      "<Template 'context_processor_home.html'>\n",
      "{'g': <flask.g of '__main__'>, 'request': <Request 'http://127.0.0.1:5000/home/' [GET]>, 'session': <NullSession {}>}\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,render_template,template_rendered\n",
    "\n",
    "app  = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello!!'\n",
    "\n",
    "@app.route('/home/')\n",
    "def home():\n",
    "    return render_template('context_processor_home.html')\n",
    "\n",
    "# 回调函数\n",
    "def render_function(sender, template, context):\n",
    "    print(sender)\n",
    "    print(template)\n",
    "    print(context)\n",
    "\n",
    "# 内置信号 与 回调函数 绑定\n",
    "template_rendered.connect(render_function)\n",
    "\n",
    "if __name__ =='__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bf50c9e",
   "metadata": {},
   "source": [
    "### got_request_exception"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "e9c11cad",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T13:26:06.924925Z",
     "start_time": "2023-12-15T13:25:52.001351Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 21:25:53] \"GET / HTTP/1.1\" 200 -\n",
      "[2023-12-15 21:25:55,542] ERROR in app: Exception on /home/ [GET]\n",
      "Traceback (most recent call last):\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/app.py\", line 2447, in wsgi_app\n",
      "    response = self.full_dispatch_request()\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/app.py\", line 1952, in full_dispatch_request\n",
      "    rv = self.handle_user_exception(e)\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/app.py\", line 1821, in handle_user_exception\n",
      "    reraise(exc_type, exc_value, tb)\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/_compat.py\", line 39, in reraise\n",
      "    raise value\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/app.py\", line 1950, in full_dispatch_request\n",
      "    rv = self.dispatch_request()\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/app.py\", line 1936, in dispatch_request\n",
      "    return self.view_functions[rule.endpoint](**req.view_args)\n",
      "  File \"/var/folders/0q/2y7xgmvn00b1090yx3d_n1140000gn/T/ipykernel_48728/857366656.py\", line 11, in home\n",
      "    num = 100 / 0\n",
      "ZeroDivisionError: division by zero\n",
      "127.0.0.1 - - [15/Dec/2023 21:25:55] \"GET /home/ HTTP/1.1\" 500 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Flask '__main__'>\n",
      "========== division by zero =============\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[2023-12-15 21:25:56,361] ERROR in app: Exception on /home/ [GET]\n",
      "Traceback (most recent call last):\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/app.py\", line 2447, in wsgi_app\n",
      "    response = self.full_dispatch_request()\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/app.py\", line 1952, in full_dispatch_request\n",
      "    rv = self.handle_user_exception(e)\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/app.py\", line 1821, in handle_user_exception\n",
      "    reraise(exc_type, exc_value, tb)\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/_compat.py\", line 39, in reraise\n",
      "    raise value\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/app.py\", line 1950, in full_dispatch_request\n",
      "    rv = self.dispatch_request()\n",
      "  File \"/Users/macbook/opt/anaconda3/lib/python3.9/site-packages/flask/app.py\", line 1936, in dispatch_request\n",
      "    return self.view_functions[rule.endpoint](**req.view_args)\n",
      "  File \"/var/folders/0q/2y7xgmvn00b1090yx3d_n1140000gn/T/ipykernel_48728/857366656.py\", line 11, in home\n",
      "    num = 100 / 0\n",
      "ZeroDivisionError: division by zero\n",
      "127.0.0.1 - - [15/Dec/2023 21:25:56] \"GET /home/ HTTP/1.1\" 500 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Flask '__main__'>\n",
      "========== division by zero =============\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask, got_request_exception\n",
    "\n",
    "app  = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    return 'Hello!!'\n",
    "\n",
    "@app.route('/home/')\n",
    "def home():\n",
    "    num = 100 / 0\n",
    "    return \"结果为：0\"\n",
    "\n",
    "def exception_log(sender, exception):\n",
    "    print(sender)\n",
    "    print('==========',exception,'=============')\n",
    "\n",
    "# 请求异常信号 与 回调函数 绑定\n",
    "got_request_exception.connect(exception_log)\n",
    "\n",
    "if __name__ =='__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c78ca61",
   "metadata": {},
   "source": [
    "## WTForms"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5427fdf8",
   "metadata": {},
   "source": [
    "![](04_Flask高级_images/8.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4847f0c",
   "metadata": {},
   "source": [
    "**这个插件库主要有两个作用：**\n",
    "1. 是做表单验证，将用户提交上来的数据进行验证是否符合系统要求。\n",
    "2. 是做模版渲染。 （了解即可）\n",
    "\n",
    "官网：https://wtforms.readthedocs.io/en/3.1.x/"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29092052",
   "metadata": {},
   "source": [
    "### 表单验证"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28d75594",
   "metadata": {},
   "source": [
    "1. 自定义一个表单类，继承自 wtforms.Form类\n",
    "\n",
    "\n",
    "2. 定义好需要验证的字段，字段的名字必须和模版中那些需要验证的input标签的name属性值保持一致\n",
    "\n",
    "\n",
    "3. 在需要验证的字段上，需要指定好具体的数据类型。\n",
    "\n",
    "\n",
    "4. 在相关的字段上，指定验证器。\n",
    "\n",
    "\n",
    "5. 以后在视图函数中，只需要使用这个表单类的对象，并且把需要验证的数据，也就是request.form传给这个表单类，再调用表单类对象.validate()方法进行，如果返回True，那么代表用户输入的数据都是符合格式要求的，Flase则代表用户输入的数据是有问题的。如果验证失败了，那么可以通过表单类对象.errors来获取具体的错误信息。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "a8e58816",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T13:35:22.104452Z",
     "start_time": "2023-12-15T13:34:42.776947Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 21:34:44] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 21:34:48] \"GET /home/ HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Dec/2023 21:34:48] \"GET /home/ HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Dec/2023 21:35:00] \"GET /register HTTP/1.1\" 308 -\n",
      "127.0.0.1 - - [15/Dec/2023 21:35:00] \"GET /register/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Flask '__main__'>\n",
      "<Template 'wtf_register.html'>\n",
      "{'g': <flask.g of '__main__'>, 'request': <Request 'http://127.0.0.1:5000/register/' [GET]>, 'session': <NullSession {}>}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "127.0.0.1 - - [15/Dec/2023 21:35:12] \"POST /register/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask, render_template, request\n",
    "from wtforms import Form, StringField\n",
    "from wtforms.validators import Length, EqualTo\n",
    "\n",
    "# 创建 app 对象\n",
    "app = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello! '\n",
    "\n",
    "# 验证表单类\n",
    "class RegisterForm(Form):\n",
    "    uname = StringField(validators=[Length(min=2,max=10,message='用户名长度2-10之间')])\n",
    "    pwd = StringField(validators=[Length(min=2,max=10)])\n",
    "    pwd2 = StringField(validators=[Length(min=2,max=10),EqualTo('pwd',message='2次密码不一致')])\n",
    "\n",
    "@app.route('/register/', methods=['GET','POST'])\n",
    "def register():\n",
    "    \"\"\"登录界面\"\"\"\n",
    "    \n",
    "    # GET请求\n",
    "    if request.method == 'GET':\n",
    "        return render_template('wtf_register.html')\n",
    "    \n",
    "    # POST请求\n",
    "    else:\n",
    "        # 检验请求对象中的 form表单\n",
    "        form = RegisterForm(request.form)\n",
    "        \n",
    "        # 验证成功：True, 失败：False\n",
    "        if form.validate():  \n",
    "            return '验证成功！'\n",
    "        else:\n",
    "            return f'验证失败！{form.errors}'\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "38f4f779",
   "metadata": {},
   "source": [
    "### 常用验证器"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d09007db",
   "metadata": {},
   "source": [
    "![](04_Flask高级_images/9.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "123c3d43",
   "metadata": {},
   "source": [
    "1. Length：字符串长度限制，有min和max两个值进行限制。\n",
    "```python\n",
    "username = StringField(validators=[Length(min=3, max=10, message=\"用户名长度必须在3到10位之间\")])\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da329c1f",
   "metadata": {},
   "source": [
    "2. EqualTo：验证数据是否和另外一个字段相等，常用的就是密码和确认密码两个字段是否相等。\n",
    "```python\n",
    "password_repeat = StringField(validators=[Length(min=6, max=10), EqualTo(\"password\")])\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14fb75b5",
   "metadata": {},
   "source": [
    "3. Email：验证上传的数据是否为邮箱数据格式 如：223333@qq.com。\n",
    "```python\n",
    "email = StringField(validators=[Email()])\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fce7cdc2",
   "metadata": {},
   "source": [
    "4. InputRequired：验证该项数据为必填项，即要求该项非空。\n",
    "```python\n",
    "username = StringField(validators=[input_required()])\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6d75369",
   "metadata": {},
   "source": [
    "5. NumberRange：数值的区间，有min和max两个值限制，如果处在这两个数字之间则满足。\n",
    "```python\n",
    "age = IntegerField(validators=[NumberRange(12,18)])\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "313292d9",
   "metadata": {},
   "source": [
    "6. Regexp：定义正则表达式进行验证，如验证手机号码。\n",
    "```python\n",
    "phone = StringField(validators=[Regexp(r'1[34578]\\d{9}')])\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98fc9dee",
   "metadata": {},
   "source": [
    "7. URL：必须是URL的形式 如http://www.bjsxt.com。\n",
    "```python\n",
    "home_page = StringField(validators=[URL()])\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9cc89a5f",
   "metadata": {},
   "source": [
    "8. UUID：验证数据是UUID类型。\n",
    "```python\n",
    "uuid = StringField(validators=[UUID()])\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "7b128526",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T13:41:35.331407Z",
     "start_time": "2023-12-15T13:41:35.324561Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "9207007c-0de1-4f73-a14c-86c7f8ae4e44\n"
     ]
    }
   ],
   "source": [
    "# 测试：生成 UUID\n",
    "from uuid import uuid4\n",
    "print(uuid4())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "6d8a29ef",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T13:51:52.831897Z",
     "start_time": "2023-12-15T13:51:32.285224Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 21:51:33] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 21:51:37] \"GET /home/ HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Dec/2023 21:51:38] \"GET /register/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Flask '__main__'>\n",
      "<Template 'wtf2_register.html'>\n",
      "{'g': <flask.g of '__main__'>, 'request': <Request 'http://127.0.0.1:5000/register/' [GET]>, 'session': <NullSession {}>}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "127.0.0.1 - - [15/Dec/2023 21:51:48] \"POST /register/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from wtforms import Form, StringField, IntegerField\n",
    "from wtforms.validators import Email, InputRequired, NumberRange, Regexp, URL, UUID\n",
    "from flask import Flask, render_template, request\n",
    "\n",
    "# 定义表单类\n",
    "class RegisterForm(Form):\n",
    "    # pip install wtforms[email]\n",
    "    email = StringField(validators=[Email()])\n",
    "    uname = StringField(validators=[InputRequired()])\n",
    "    age = IntegerField(validators=[NumberRange(min=18, max=50)])\n",
    "    phone = StringField(validators=[Regexp('^1[3456789]\\d{9}$')])\n",
    "    homepage = StringField(validators=[URL()])\n",
    "    uuid = StringField(validators=[UUID()])\n",
    "    \n",
    "# 创建 App 对象\n",
    "app = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello! '\n",
    "\n",
    "\n",
    "@app.route('/register/', methods=['GET','POST'])\n",
    "def register():\n",
    "    \n",
    "    # GET请求\n",
    "    if request.method == 'GET':\n",
    "        return render_template('wtf2_register.html')\n",
    "    \n",
    "    # POST请求\n",
    "    else:\n",
    "        \n",
    "        # 验证 请求对象 内的 form表单\n",
    "        form = RegisterForm(request.form)\n",
    "        if form.validate():\n",
    "            return '验证成功！'\n",
    "        else:\n",
    "            return f'验证失败！{form.errors}'\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "346ce4cf",
   "metadata": {},
   "source": [
    "### 自定义验证器"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "525d5d0f",
   "metadata": {},
   "source": [
    "**自定义验证器步骤如下：**\n",
    "\n",
    "1 定义一个方法，方法的名字规则是： validate_字段名(self, field) \n",
    "\n",
    "\n",
    "2 在方法中，使用 field.data 可以获取到这个字段的具体的值。\n",
    "\n",
    "\n",
    "3 验证时，如果数据满足条件，那么可以什么都不做。如果验证失败，那么应该抛出一个 wtforms.validators.ValidationError 的异常，并且把验证失败的信息传到这个异常类中"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "89ab1daf",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T14:42:37.315553Z",
     "start_time": "2023-12-15T14:42:07.032922Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 22:42:08] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 22:42:11] \"GET /home/ HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Dec/2023 22:42:12] \"GET /register/ HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Dec/2023 22:42:21] \"GET /login/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Flask '__main__'>\n",
      "<Template 'wtf3_login.html'>\n",
      "{'code': 5309, 'g': <flask.g of '__main__'>, 'request': <Request 'http://127.0.0.1:5000/login/' [GET]>, 'session': <SecureCookieSession {'code': 5309}>}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "127.0.0.1 - - [15/Dec/2023 22:42:32] \"POST /login/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "前端的数据是：5309 ==== 服务器端的数据是：5309\n",
      "前端的数据是：<class 'str'> ==== 服务器端的数据是：<class 'str'>\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,render_template,request,session\n",
    "from random import randint\n",
    "from wtforms import Form,StringField\n",
    "from wtforms.validators import Length, ValidationError\n",
    "\n",
    "# 创建 app 对象\n",
    "app = Flask(__name__)\n",
    "\n",
    "# 设置 session 盐\n",
    "app.config['SECRET_KEY'] = 'sdfasdfasdf'\n",
    "\n",
    "# 定义表单类\n",
    "class LoginForm(Form):\n",
    "    \n",
    "    # 长度为4的验证码\n",
    "    code = StringField(validators=[Length(4,4)]) # 不能满足数据内容的匹配\n",
    "\n",
    "    def validate_code(self, field):\n",
    "        \"\"\"自定义验证器\"\"\"\n",
    "        \n",
    "        # 获取字段的值\n",
    "        font_code = field.data\n",
    "        \n",
    "        # 正确的值\n",
    "        server_code = str(session.get('code'))\n",
    "\n",
    "        print(f'前端的数据是：{font_code} ==== 服务器端的数据是：{server_code}')\n",
    "        print(f'前端的数据是：{type(font_code)} ==== 服务器端的数据是：{type(server_code)}')\n",
    "        \n",
    "        # 验证失败\n",
    "        if font_code != server_code:\n",
    "            raise ValidationError('验证码错误！')\n",
    "\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello! '\n",
    "\n",
    "\n",
    "@app.route('/login/', methods=['GET','POST'])\n",
    "def login():\n",
    "    \n",
    "    # GET请求\n",
    "    if request.method == 'GET':\n",
    "        \n",
    "        # 生成验证码\n",
    "        code = randint(1000, 9999)\n",
    "        \n",
    "        # 设置session\n",
    "        session['code'] = code\n",
    "        \n",
    "        # 返回模版\n",
    "        return render_template('wtf3_login.html',code=code)\n",
    "    \n",
    "    # POST请求\n",
    "    else:\n",
    "        \n",
    "        # 验证表单\n",
    "        form = LoginForm(request.form)\n",
    "        \n",
    "        # 返回响应\n",
    "        if form.validate():\n",
    "            return '验证成功！'\n",
    "        else:\n",
    "            return f'验证失败！{form.errors}'\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37faec79",
   "metadata": {},
   "source": [
    "### 渲染模版"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4968236e",
   "metadata": {},
   "source": [
    "```html\n",
    "<style type=\"text/css\">\n",
    "    .hot{\n",
    "        background-color: cadetblue;\n",
    "    }\n",
    "</style>\n",
    "\n",
    "<form action=\"/login/\" method=\"post\">\n",
    "        <table>\n",
    "            <tr>\n",
    "                <td>{{ form.uname.label}}</td>\n",
    "                <td>{{ form.uname(class='hot')}}</td>\n",
    "            </tr>\n",
    "            <tr>\n",
    "                <td> {{ form.age.label}}</td>\n",
    "                <td>{{ form.age()}}</td>\n",
    "            </tr>\n",
    "            <tr>\n",
    "                <td> {{ form.remember.label}}</td>\n",
    "                <td>{{ form.remember()}}</td>\n",
    "            </tr>\n",
    "            <tr>\n",
    "                <td>{{ form.address.label}}</td>\n",
    "                <td>{{ form.address()}}</td>\n",
    "            </tr>\n",
    "            <tr>\n",
    "                <td></td>\n",
    "                <td></td>\n",
    "            </tr>\n",
    "        </table>\n",
    "    </form>\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "f56081f5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T14:45:09.242459Z",
     "start_time": "2023-12-15T14:44:57.200921Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 22:44:59] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 22:45:02] \"GET /home/ HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Dec/2023 22:45:03] \"GET /login/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Flask '__main__'>\n",
      "<Template 'wtf4_login.html'>\n",
      "{'form': <__main__.LoginForm object at 0x112001370>, 'g': <flask.g of '__main__'>, 'request': <Request 'http://127.0.0.1:5000/login/' [GET]>, 'session': <NullSession {}>}\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,render_template\n",
    "from wtforms import Form,StringField,IntegerField,BooleanField,SelectField\n",
    "from wtforms.validators import Length,InputRequired\n",
    "\n",
    "# 定义表单类\n",
    "class LoginForm(Form):\n",
    "    uname = StringField(validators=[Length(min=1,max=10)])\n",
    "    age = IntegerField('年龄:',validators=[InputRequired()])\n",
    "    remember = BooleanField('记住我：')\n",
    "    address = SelectField('地址:',choices=[('bj','北京'),('sh','上海'),('gz','广州')])\n",
    "\n",
    "# 创建 app类\n",
    "app = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello! '\n",
    "\n",
    "@app.route('/login/')\n",
    "def login():\n",
    "    \n",
    "    # 表单\n",
    "    form = LoginForm()\n",
    "    \n",
    "    # 返回模版，传入表单\n",
    "    return render_template('wtf4_login.html',form = form)\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51157541",
   "metadata": {},
   "source": [
    "## Flask 上传文件 / 访问文件"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dde930fe",
   "metadata": {},
   "source": [
    "**上传文件步骤：**\n",
    "1. 在模版html中，表单需要指定 enctype='multipart/form-data' 才能上传文件\n",
    "\n",
    "\n",
    "2. 在后台如果想要获取上传的文件，那么应该使用 request.files.get('文件名')  来获取\n",
    "\n",
    "\n",
    "3. 保存文件之前，先要使用 werkzeug.utils.secure_filename 来对上传上来的文件名进行一个过滤。能保证不会有安全问题。\n",
    "\n",
    "\n",
    "4. 获取到上传上来的文件后，使用 文件对象.save(路径) 方法来保存文件。路径=完整路径=路径名+文件名"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f9b701d",
   "metadata": {},
   "source": [
    "**访问文件：**\n",
    "\n",
    "从服务器上读取文件，应该定义一个url与视图函数，来获取指定的文件\n",
    "\n",
    "在这个视图函数中，使用 send_from_directory(文件的目录, 文件名) 来获取。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58112b91",
   "metadata": {},
   "source": [
    "```html\n",
    "<form action=\"/upload/\" method=\"post\" enctype=\"multipart/form-data\">\n",
    "    上传文件：<input type=\"file\" name=\"pic\"><br>\n",
    "    <input type=\"submit\" value=\"提交\">\n",
    "</form>\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "be6ee786",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T15:59:01.850617Z",
     "start_time": "2023-12-15T15:58:28.164023Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Dec/2023 23:58:31] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Dec/2023 23:58:45] \"GET /download/Python_GUIPyQt5_.pdf HTTP/1.1\" 308 -\n",
      "127.0.0.1 - - [15/Dec/2023 23:58:45] \"GET /download/Python_GUIPyQt5_.pdf/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,render_template,request,send_from_directory\n",
    "from werkzeug.utils import secure_filename\n",
    "import os\n",
    "\n",
    "# 创建 App 对象\n",
    "app = Flask(__name__)\n",
    "\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello! '\n",
    "\n",
    "@app.route('/upload/',methods=['GET','POST'])\n",
    "def upload():\n",
    "    \n",
    "    # GET请求\n",
    "    if request.method == 'GET':\n",
    "        return render_template('file_index.html')\n",
    "    \n",
    "    # POST请求\n",
    "    else:\n",
    "        \n",
    "        # 在 请求对象中 获取 图片路径\n",
    "        img_file = request.files.get('pic')\n",
    "        file_name = img_file.filename\n",
    "        \n",
    "        # 文件名的安全转换\n",
    "        filename = secure_filename(file_name)\n",
    "        \n",
    "        # 保存文件至本地\n",
    "        img_file.save(os.path.join(\"./data/\", filename))\n",
    "        \n",
    "        return '上传文件成功！'\n",
    "\n",
    "@app.route('/download/<filename>/')\n",
    "def download_file(filename):\n",
    "    \"\"\"将文件上传至云端\"\"\"\n",
    "    return send_from_directory(\"./data/\", filename)\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa1feb2c",
   "metadata": {},
   "source": [
    "## flask-wtf 上传文件"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9880678",
   "metadata": {},
   "source": [
    "![](04_Flask高级_images/10.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79e5ee49",
   "metadata": {},
   "source": [
    "**关键点：**\n",
    "\n",
    "1 定义验证表单类的时候，对文件类型的字段，需要采用 FileField 这个类型，即 wtforms.FileField\n",
    "\n",
    "2 验证器需要从 flask_wtf.file 中导入 flask_wtf.file.FileRequired 和 flask_wtf.file.FileAllowed\n",
    "\n",
    "3 flask_wtf.file.FileRequired 是用来验证文件上传不能为空。 \n",
    "\n",
    "4 flask_wtf.file.FileAllowed 用来验证上传的文件的后缀名， 如常见图片后缀 .jpg 和.png以及.gif等。 \n",
    "\n",
    "5 在视图函数中，需要使用 from werkzeug.datastructures import CombinedMultiDict 来把 request.form 与 request.files 来进行合并。\n",
    "\n",
    "6 最后使用 表单验证对象.validate()进行验证。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "70c6fa62",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T16:08:54.376102Z",
     "start_time": "2023-12-15T16:08:25.404171Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [16/Dec/2023 00:08:26] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [16/Dec/2023 00:08:34] \"GET /upload/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Flask '__main__'>\n",
      "<Template 'file_index.html'>\n",
      "{'g': <flask.g of '__main__'>, 'request': <Request 'http://127.0.0.1:5000/upload/' [GET]>, 'session': <NullSession {}>}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "127.0.0.1 - - [16/Dec/2023 00:08:39] \"POST /upload/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ImmutableMultiDict([]) ImmutableMultiDict([('pic', <FileStorage: 'cc.jpg' ('image/jpeg')>)])\n",
      "<input id=\"pic\" name=\"pic\" required type=\"file\">\n"
     ]
    }
   ],
   "source": [
    "from wtforms import Form,FileField\n",
    "from flask_wtf.file import FileAllowed,FileRequired\n",
    "from flask import Flask,render_template,request\n",
    "from werkzeug.datastructures import CombinedMultiDict\n",
    "from werkzeug.utils import secure_filename\n",
    "import os\n",
    "\n",
    "# 定义表单类\n",
    "class UpLoadForm(Form):\n",
    "    pic = FileField(validators=[FileRequired(), # 文件不为空\n",
    "                                FileAllowed(['jpg','png','gif'])]) # 文件后缀名特定\n",
    "    \n",
    "# 创建 App 对象\n",
    "app = Flask(__name__)\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \"\"\"主页面\"\"\"\n",
    "    return 'Hello! '\n",
    "\n",
    "@app.route('/upload/',methods=['GET','POST'])\n",
    "def upload():\n",
    "    \n",
    "    # GET请求\n",
    "    if request.method == 'GET':\n",
    "        return render_template('file_index.html')\n",
    "    \n",
    "    # POST请求\n",
    "    else:\n",
    "        \n",
    "        # 获取 请求对象 中的表单\n",
    "        form = UpLoadForm(CombinedMultiDict([request.form, request.files]))\n",
    "        \n",
    "        # 验证文件\n",
    "        if form.validate():\n",
    "            \n",
    "            # 读取文件名\n",
    "            img_file = form.pic.data\n",
    "            file_name = secure_filename(img_file.filename)\n",
    "            \n",
    "            # 保存至本地\n",
    "            img_file.save(os.path.join(\"./data/\",file_name))\n",
    "            return '上传文件成功！'\n",
    "        else:\n",
    "            return f'{form.errors}'\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c5510528",
   "metadata": {},
   "source": [
    "## RestFul"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "557b3f3a",
   "metadata": {},
   "source": [
    "REST 指的是一组架构约束条件和原则。\n",
    "\n",
    "满足这些约束条件和原则的应用程序或设计就是 RESTful。\n",
    "\n",
    "RESTful是一种软件架构风格、设计风格，而不是标准，只是提供了一组设计原则和约束条件。\n",
    "\n",
    "它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁，更有层次。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "530a96e9",
   "metadata": {},
   "source": [
    "**数据传输格式：**\n",
    "- 数据传输的格式应该都用json格式。\n",
    "\n",
    "**url链接规则：**\n",
    "- url链接中，不能有动词，只能有名词。\n",
    "- 并且对于一些名词，如果出现复数，那么应该在后面加s。\n",
    "- 比如：获取新闻列表，应该使用 /news/ ，而不应该使用/get_news/\n",
    "\n",
    "**HTTP请求方式：**\n",
    "- GET：从服务器上获取资源。\n",
    "- POST：在服务器上新增或者修改一个资源。\n",
    "- PUT：在服务器上更新资源。（客户端提供所有改变后的数据）\n",
    "- PATCH：在服务器上更新资源。（客户端只提供需要改变的属性）\n",
    "- DELETE：从服务器上删除资源。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54b7b2b2",
   "metadata": {},
   "source": [
    "**状态码：**\n",
    "\n",
    "![](04_Flask高级_images/11.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8fb0c3c9",
   "metadata": {},
   "source": [
    "### 基本使用"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "745be9e9",
   "metadata": {},
   "source": [
    "**定义Restful的类视图：**\n",
    "1. 从 flask_restful 中导入 Api ，来创建一个 api 对象\n",
    "2. 写一个类视图，让他继承自 Resource 类，然后在这个里面，使用你想要的请求方式来定义相应的方法，比如你想要将这个类视图只能采用 post 请求，那么就定义一个 post 方法。\n",
    "3. 使用 api.add_resource 来添加类视图与 url"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "66a9f329",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T16:19:52.858099Z",
     "start_time": "2023-12-15T16:19:00.690242Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/login/\n",
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [16/Dec/2023 00:19:04] \"GET / HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [16/Dec/2023 00:19:11] \"GET /login/ HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [16/Dec/2023 00:19:32] \"GET /login2/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask, url_for\n",
    "from flask_restful import Resource, Api\n",
    "\n",
    "# 创建 App 对象\n",
    "app = Flask(__name__)\n",
    "\n",
    "# 建立Api对象，并绑定应用APP\n",
    "api = Api(app)\n",
    "\n",
    "# 类视图\n",
    "class LoginView(Resource):\n",
    "    def get(self):\n",
    "        return {\"flag\":True}\n",
    "    def post(self):\n",
    "        return {\"flag\":False}\n",
    "\n",
    "# 建立路由映射\n",
    "api.add_resource(LoginView,'/login/','/login2/',endpoint='login')\n",
    "\n",
    "with app.test_request_context():\n",
    "    print(url_for('login'))\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "78f562a9",
   "metadata": {},
   "source": [
    "### 参数验证"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a54a56e",
   "metadata": {},
   "source": [
    "基本用法：\n",
    "1. 通过 flask_restful.reqparse 中 RequestParser 建立解析器\n",
    "2. 通过 RequestParser 中的 add_argument 方法定义字段与解析规则\n",
    "3. 通过 RequestParser 中的 parse_args 来解析参数\n",
    "    1. 解析正确，返回正确参数\n",
    "    2. 解析错误，返回错误信息给前端"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dfa58247",
   "metadata": {},
   "outputs": [],
   "source": [
    "from flask import Flask\n",
    "from flask_restful import Api,Resource\n",
    "from flask_restful.reqparse import RequestParser\n",
    "\n",
    "# 使用 RestFul 风格\n",
    "app = Flask(__name__)\n",
    "api = Api(app)\n",
    "\n",
    "# 类视图\n",
    "class RegisterView(Resource):\n",
    "    def post(self):\n",
    "        # 建立解析器\n",
    "        parser = RequestParser()\n",
    "        \n",
    "        # 定义数据的解析规则\n",
    "        parser.add_argument('uname',type=str,required=True,help='用户名验证错误',trim=True)\n",
    "        \n",
    "        # 解析数据\n",
    "        args = parser.parse_args()\n",
    "        \n",
    "        # 正确，直接获取参数\n",
    "        # 错误，回馈到前端\n",
    "        print(args)\n",
    "            \n",
    "        \n",
    "        # 响应数据\n",
    "        return {'msg':'注册成功！！'}\n",
    "    \n",
    "# 建立映射关系\n",
    "api.add_resource(RegisterView, '/register/')\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb21658e",
   "metadata": {},
   "source": [
    "### add_argument方法"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9fa2610e",
   "metadata": {},
   "source": [
    "add_argument方法 可以指定这个字段的名字，这个字段的数据类型等，验证错误提示信息等，具体如下：\n",
    "\n",
    "**1 default：** 默认值，如果这个参数没有值，那么将使用这个参数指定的默认值。\n",
    "\n",
    "**2 required：** 是否必须。默认为False，如果设置为True，那么这个参数就必须提交上来\n",
    "\n",
    "**3 type：** 这个参数的数据类型，如果指定，那么将使用指定的数据类型来强制转换提交上来的值。可以使用python自带的一些数据类型(如str或int)，也可以使用flask_restful.inputs下的一些特定的数据类型来强制转换。\n",
    "\n",
    "- url：会判断这个参数的值是否是一个url，如果不是，那么就会抛出异常。\n",
    "\n",
    "- regex：正则表达式。\n",
    "\n",
    "- date：将这个字符串转换为datetime.date数据类型。如果转换不成功，则会抛出一个异常.\n",
    "\n",
    "**4 choices：** 固定选项。提交上来的值只有满足这个选项中的值才符合验证通过，否则验证不通过。\n",
    "\n",
    "**5 help：** 错误信息。如果验证失败后，将会使用这个参数指定的值作为错误信息。\n",
    "\n",
    "**6 trim：** 是否要去掉前后的空格。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79a2d3a9",
   "metadata": {},
   "outputs": [],
   "source": [
    "from flask import Flask\n",
    "from flask_restful import Api,Resource,inputs\n",
    "from flask_restful.reqparse import RequestParser\n",
    "\n",
    "app = Flask(__name__)\n",
    "api = Api(app)\n",
    "\n",
    "class RegisterView(Resource):\n",
    "    def post(self):\n",
    "        \n",
    "        # 建立解析器\n",
    "        parser = RequestParser()\n",
    "        \n",
    "        # 定义解析规则\n",
    "        parser.add_argument('uname',type=str,required=True,trim=True,help='用户名不符合规范')\n",
    "        parser.add_argument('pwd',type=str,help='密码错误',default='123456')\n",
    "        parser.add_argument('age',type=int,help='年龄验证错误！')\n",
    "        parser.add_argument('gender',type=str,choices=['男', '女','保密'],help='性别验证错误')\n",
    "        parser.add_argument('birthday',type=inputs.date,help='生日验证错误')\n",
    "        parser.add_argument('phone',type=inputs.regex('^1[356789]\\d{9}$'),help='电话验证错误')\n",
    "        parser.add_argument('homepage',type=inputs.url,help='个人主页验证错误')\n",
    "        \n",
    "        # 解析数据\n",
    "        args = parser.parse_args()\n",
    "        print(args)\n",
    "        \n",
    "        return {'msg':'注册成功！'}\n",
    "    \n",
    "api.add_resource(RegisterView, '/register/')\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f476e9e0",
   "metadata": {},
   "source": [
    "### 规范返回值"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c11ec33",
   "metadata": {},
   "source": [
    "导入 flask_restful.marshal_with 装饰器\n",
    "\n",
    "定义一个字典变量来指定需要返回的标准化字段，以及该字段的数据类型\n",
    "\n",
    "在请求方法中，返回自定义对象的时候，flask_restful会自动的读取对象模型上的所有属性。\n",
    "\n",
    "组装成一个符合标准化参数的json格式字符串返回给客户端"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "7d8ce7f8",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T16:32:17.519009Z",
     "start_time": "2023-12-15T16:32:04.997636Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [16/Dec/2023 00:32:06] \"GET / HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [16/Dec/2023 00:32:11] \"GET /news HTTP/1.1\" 308 -\n",
      "127.0.0.1 - - [16/Dec/2023 00:32:11] \"GET /news/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask\n",
    "from flask_restful import Api,Resource,fields,marshal_with\n",
    "\n",
    "# 遵循 RestFul 规范\n",
    "app = Flask(__name__)\n",
    "api = Api(app)\n",
    "\n",
    "class News:\n",
    "    \"\"\"消息类\"\"\"\n",
    "    def __init__(self, code, msg, state, content):\n",
    "        self.code = code\n",
    "        self.msg = msg\n",
    "        self.state1 =state\n",
    "        self.content = content\n",
    "\n",
    "class NewsView(Resource):\n",
    "    \"\"\"消息视图\"\"\"\n",
    "    \n",
    "    # 定义返回字段的规范\n",
    "    resouce_fields ={\n",
    "        'code':fields.Integer,\n",
    "        'msg':fields.String,\n",
    "        'state':fields.String\n",
    "    }\n",
    "\n",
    "    @marshal_with(resouce_fields)\n",
    "    def get(self):\n",
    "        \n",
    "        # 缺乏的字段，用 null 填充\n",
    "        return {'code':200,'msg':'访问成功！'}\n",
    "\n",
    "    @marshal_with(resouce_fields)\n",
    "    def post(self):\n",
    "        return {'msg':'注册成功！','state':'移动'}\n",
    "    \n",
    "    @marshal_with(resouce_fields)\n",
    "    def put(self):\n",
    "        # 在返回对象时，会自动在对象中获取与约定好的字段，并获取封装成json。\n",
    "        news = News(404, 'OK', '电脑端','尚学堂')\n",
    "        return news\n",
    "    \n",
    "api.add_resource(NewsView,'/news/')\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50f3c41e",
   "metadata": {},
   "source": [
    "#### 参数设置"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb9d1e13",
   "metadata": {},
   "source": [
    "**规范给出的属性名 和 模型内部的属性名 不相同**\n",
    "- 使用 attribute 配置这种映射,比如： fields.String(attribute='username')\n",
    "\n",
    "**某些字段，没有值，但想给一个值做为默认值**\n",
    "- 使用 default 指定默认值，比如： fields.String(default='sxt')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "7c4f5589",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T16:37:35.466328Z",
     "start_time": "2023-12-15T16:37:09.287658Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [16/Dec/2023 00:37:13] \"GET / HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [16/Dec/2023 00:37:18] \"GET /news/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask\n",
    "from flask_restful import Api,Resource,fields,marshal_with\n",
    "\n",
    "# 遵守 RestFul 规范\n",
    "app = Flask(__name__)\n",
    "api = Api(app)\n",
    "\n",
    "class News:\n",
    "    \"\"\"消息对象\"\"\"\n",
    "    def __init__(self,code,msg,info):\n",
    "        self.code = code\n",
    "        self.msg = msg\n",
    "        self.info = info\n",
    "        self.state = 1000\n",
    "        \n",
    "class NewsView(Resource):\n",
    "    \n",
    "    # 设置 返回规范\n",
    "    resouce_fields ={\n",
    "        'code':fields.Integer(default=200), # 通过 参数default 来设置 默认值\n",
    "        'msg':fields.String, \n",
    "        'content':fields.String(attribute='info'),  # 通过 参数attribute 来设置 提取数据的字段\n",
    "        'state':fields.Integer(default=2000) # 优先级不如真实数据里面的高\n",
    "    }\n",
    "\n",
    "    @marshal_with(resouce_fields)\n",
    "    def get(self):\n",
    "        news = News(200, '访问成功！','移动')\n",
    "        return news\n",
    "\n",
    "    @marshal_with(resouce_fields)\n",
    "    def post(self):\n",
    "        return {'msg':'增加数据成功！','info':'联通'}\n",
    "\n",
    "    @marshal_with(resouce_fields)\n",
    "    def put(self):\n",
    "        news = News(200,'访问成功！','移动')\n",
    "        return news\n",
    "    \n",
    "api.add_resource(NewsView,'/news/')\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d99789c4",
   "metadata": {},
   "source": [
    "#### 类型设置"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bcd2b8bf",
   "metadata": {},
   "source": [
    "返回的值里有json或者列表数据，这时可以通过以字段来实现\n",
    "- fields.List 放置一个列表\n",
    "- fields.Nested放置一个字典"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "93d623d0",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T16:43:17.622485Z",
     "start_time": "2023-12-15T16:42:51.865009Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [16/Dec/2023 00:42:57] \"GET / HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [16/Dec/2023 00:43:02] \"GET /news/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask\n",
    "from flask_restful import Api,Resource,fields,marshal_with\n",
    "\n",
    "app = Flask(__name__)\n",
    "api = Api(app)\n",
    "\n",
    "class User:\n",
    "    \"\"\"用户\"\"\"\n",
    "    def __init__(self,uname):\n",
    "        self.uname = uname\n",
    "   \n",
    "    def __repr__(self):\n",
    "        return f'<User uname:{self.uname}>'\n",
    "\n",
    "class NewsType:\n",
    "    \"\"\"消息类型\"\"\"\n",
    "    def __init__(self,_type):\n",
    "        self._type = _type\n",
    "\n",
    "    def __repr__(self):\n",
    "        return f'<User type:{self._type}>'\n",
    "    \n",
    "class News:\n",
    "    \"\"\"消息\"\"\"\n",
    "    def __init__(self,code,msg):\n",
    "        self.code = code\n",
    "        self.msg = msg\n",
    "        self.user = None\n",
    "        self._type = []\n",
    "\n",
    "    def __repr__(self):\n",
    "        return f'<News code:{self.code} msg:{self.msg} user:{self.user} type:{self._type}>'\n",
    "\n",
    "def create_data():\n",
    "    \"\"\"创建数据\"\"\"\n",
    "    user = User('尚学堂')\n",
    "    _type1 = NewsType('IT')\n",
    "    _type2 = NewsType('Python')\n",
    "    news = News(200, 'Python又双叕更新了！')\n",
    "    news.user = user\n",
    "    news._type.append(_type1)\n",
    "    news._type.append(_type2)\n",
    "\n",
    "    return news\n",
    "\n",
    "\n",
    "class NewsView(Resource):\n",
    "    \"\"\"消息视图\"\"\"\n",
    "    \n",
    "    # 规范\n",
    "    resouce_fields ={\n",
    "        'code':fields.Integer,\n",
    "        'msg':fields.String,\n",
    "        \n",
    "        # 字典类型\n",
    "        'user':fields.Nested({\n",
    "            'uname':fields.String\n",
    "        }),\n",
    "        \n",
    "        # 列表类型，内部存储字典类型\n",
    "        '_type':fields.List(fields.Nested({\n",
    "            '_type':fields.String\n",
    "        }))\n",
    "    }\n",
    "\n",
    "    @marshal_with(resouce_fields)\n",
    "    def get(self):\n",
    "        news = create_data()\n",
    "        return news\n",
    "\n",
    "api.add_resource(NewsView,'/news/')\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de8ff5f9",
   "metadata": {},
   "source": [
    "### 结合蓝图使用"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2570438b",
   "metadata": {},
   "source": [
    "在蓝图中，如果使用Flask_RESTful，\n",
    "\n",
    "创建Api对象的时候，传入蓝图对象即可，不再是传入 app 对象"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "7ca24b4c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T16:46:28.094100Z",
     "start_time": "2023-12-15T16:46:13.261871Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [16/Dec/2023 00:46:14] \"GET / HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [16/Dec/2023 00:46:21] \"GET /login/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask\n",
    "\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "from user import user_bp\n",
    "app.register_blueprint(user_bp)\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfda1487",
   "metadata": {},
   "source": [
    "### 渲染模版"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c41fc7aa",
   "metadata": {},
   "source": [
    "渲染模版 就是在 Flask_RESTful的类视图中 要返回html片段代码，或者是整个html文件代码。\n",
    "\n",
    "如何需要浏览器渲染模板内容应该使用 api.representation 这个装饰器来定义一个函数，\n",
    "\n",
    "在这个函数中，应该对 html 代码进行一个封装，再返回。\n",
    "\n",
    "注意: api.representation装饰器修饰的函数必须返回一个Response对象"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "331d6cbe",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-12-15T16:49:46.121682Z",
     "start_time": "2023-12-15T16:49:41.505179Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask,render_template,Response\n",
    "from flask_restful import Api,Resource\n",
    "import json\n",
    "\n",
    "# 遵守 RestFul 规范\n",
    "app = Flask(__name__)\n",
    "# 如果想要前端的中文不再是\\u这样的编码，可以加如下参数配置\n",
    "app.config['RESTFUL_JSON'] = dict(ensure_ascii=False)\n",
    "api = Api(app)\n",
    "\n",
    "# 视图\n",
    "class HomeView(Resource):\n",
    "    def get(self):\n",
    "        return {\"msg\":\"这个是个人主页\"}\n",
    "\n",
    "class IndexView(Resource):\n",
    "    def get(self):\n",
    "        return render_template('index.html')\n",
    "\n",
    "api.add_resource(IndexView,'/index/')\n",
    "api.add_resource(HomeView,'/home/')\n",
    "\n",
    "@api.representation('text/html')\n",
    "def out_html(data,code,headers):\n",
    "    # 必须返回一个response对象\n",
    "    if isinstance(data, str):\n",
    "        resp = Response(data)\n",
    "        return resp\n",
    "    else:\n",
    "        return Response(json.dumps(data,ensure_ascii=False).encode('gbk'))\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "afd62b8c",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6c8e0780",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d614c4a4",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "02b49c29",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "288px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
